// Copyright (c) 2014 Luca Marturana. All rights reserved.
// Licensed under Apache 2.0, see LICENSE for details

#include <redis3m/connection.h>
#include <boost/assign/list_of.hpp>
#include <hiredis/hiredis.h>
#include <algorithm>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>

using namespace redis3m;

connection::connection(const std::string& host, const unsigned port, const unsigned timeout)
{
    std::string ip = get_random_ip(host);//add by leeyan, 2014-11-14
    c = redisConnect(ip.c_str(), port);
    if (c->err != REDIS_OK)
    {
        throw unable_to_connect();
    }

    // timeout
    if (timeout > 0)
    {
	    struct timeval tv;
	    tv.tv_sec = timeout;
	    tv.tv_usec = 0;
	    if (redisSetTimeout(c, tv) != REDIS_OK)
	    {
		    throw unable_to_connect();
	    }
    } 
}

connection::~connection()
{
    redisFree(c);
}

void connection::append(const std::vector<std::string> &commands)
{
    std::vector<const char*> argv;
    argv.reserve(commands.size());
    std::vector<size_t> argvlen;
    argvlen.reserve(commands.size());

    for (std::vector<std::string>::const_iterator it = commands.begin(); it != commands.end(); ++it) {
        argv.push_back(it->c_str());
        argvlen.push_back(it->size());
    }

    int ret = redisAppendCommandArgv(c, static_cast<int>(commands.size()), argv.data(), argvlen.data());
    if (ret != REDIS_OK)
    {
        throw transport_failure();
    }
}

reply connection::get_reply()
{
    redisReply *r;
    int error = redisGetReply(c, reinterpret_cast<void**>(&r));
    if (error != REDIS_OK)
    {
        throw transport_failure();
    }
    reply ret(r);
    freeReplyObject(r);
    return ret;
}

std::vector<reply> connection::get_replies(unsigned int count)
{
    std::vector<reply> ret;
    for (int i=0; i < count; ++i)
    {
        ret.push_back(get_reply());
    }
    return ret;
}

bool connection::is_valid()
{
    return c->err == REDIS_OK;
}

std::string connection::get_random_ip(const std::string & host)
{
    std::vector<std::string> ip_list;
    struct addrinfo *answer, hint, *curr;
    char ipstr[16];
    bzero(&hint, sizeof(hint));
    hint.ai_family = AF_INET;
    hint.ai_socktype = SOCK_STREAM;
    int ret = getaddrinfo(host.c_str(), NULL, &hint, &answer);
    if (ret != 0) 
    {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret));
        throw unable_to_connect();
    }

    for (curr = answer; curr != NULL; curr = curr->ai_next) 
    {
        inet_ntop(AF_INET, &(((struct sockaddr_in *)(curr->ai_addr))->sin_addr), ipstr, 16);
        ip_list.push_back(ipstr);
    }

    sort(ip_list.begin(), ip_list.end());

    freeaddrinfo(answer);

    struct timeval tv;
    gettimeofday(&tv, NULL);
    unsigned seed = (tv.tv_sec * 1000 + tv.tv_usec / 1000) % 1000000000;
    srand(seed);
    size_t index = rand() % (ip_list.size());

    return ip_list[index];
}
